﻿using Pws.Clients.RestLibrary.ECommerce.Users;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Pws.Clients.RestLibrary.Service
{
	/// <summary>
	/// Some useful extension methods to the RestLibrary .NET Objects.
	/// </summary>
	public static class RestLibraryExtensionMethods
	{
		#region Links
		/// <summary>
		/// Most objects contain a list of links to other objects. These links are denoted by fixed
		/// identifier references.
		/// If the identifier with the specified relationship is not found, then an exception is thrown.
		/// </summary>
		/// <param name="pwsObject">An IPwsObject_V1 which contains a list of Link_V1 objects called Links.</param>
		/// <param name="identifier">The identifier of the Link_V1 object to look for, e.g. "order".</param>
		/// <param name="rel">The relationship of the Link_V1 object to look for, e.g. "edit".</param>
		/// <returns>A Link_V1 object.</returns>
		public static Link_V1 FindLink(this IPwsObject_V1 pwsObject, String identifier, String rel)
		{
			if (pwsObject == null)
				throw new ArgumentNullException("pwsObject", "FindLink requires a non-null PWS Object");

			if (String.IsNullOrEmpty("rel"))
				throw new ArgumentNullException("rel", "FindLink requires a relationship to find");

			if (String.IsNullOrEmpty("identifier"))
				throw new ArgumentNullException("identifier", "FindLink requires an identifier to find");

			Link_V1 result = pwsObject.Links.Where(f => String.Equals(f.Identifier, identifier, StringComparison.CurrentCultureIgnoreCase) && String.Equals(f.Rel, rel, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();

			if (result == null)
				throw new ArgumentOutOfRangeException(identifier, "FindLink could not find the identifier within the links");

			return result;
		}

		/// <summary>
		/// Most objects contain a list of links to other objects. These links are denoted by fixed
		/// identifier references. This overload does not look for a specific relationship, but instead returns
		/// the first link corresponding to the given identifier.
		/// </summary>
		/// <param name="pwsObject">An IPwsObject_V1 which contains a list of Link_V1 objects called Links.</param>
		/// <param name="identifier">The identifier of the Link_V1 object to look for, e.g. "order".</param>
		/// <returns>A Link_V1 object.</returns>
		public static Link_V1 FindLink(this IPwsObject_V1 pwsObject, String identifier)
		{
			if (pwsObject == null)
				throw new ArgumentNullException("pwsObject", "FindLink requires a non-null PWS Object");

			if (String.IsNullOrEmpty("identifier"))
				throw new ArgumentNullException("identifier", "FindLink requires an identifier to find");

			Link_V1 result = pwsObject.Links.Where(f => String.Equals(f.Identifier, identifier, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();

			if (result == null)
				throw new ArgumentOutOfRangeException(identifier, "FindLink could not find the identifier within the links");

			return result;
		}

		/// <summary>
		/// True if this object has a "self" link.
		/// Most objects contain a list of links to other objects. One of these links is often a "self" link,
		/// which returns the same object back again with its most recent data.
		/// </summary>
		/// <param name="pwsObject">An IPwsObject_V1 which contains a list of Link_V1 objects called Links.</param>
		/// <returns>True if a "self" link exists, false if not.</returns>
		public static bool HasSelfLink(this IPwsObject_V1 pwsObject)
		{
			return pwsObject.Links.Where(f => String.Equals(f.Rel, "self", StringComparison.CurrentCultureIgnoreCase)).Any();
		}

		/// <summary>
		/// Most objects contain a list of links to other objects. One of these links is often a "self" link,
		/// which returns the same object back again with its most recent data.
		/// </summary>
		/// <param name="pwsObject">An IPwsObject_V1 which contains a list of Link_V1 objects called Links.</param>
		/// <returns>A Link_V1 object.</returns>
		public static Link_V1 FindSelfLink(this IPwsObject_V1 pwsObject)
		{
			if (pwsObject == null)
				throw new ArgumentNullException("pwsObject", "FindSelfLink requires a non-null PWS Object");

			Link_V1 result = pwsObject.Links.Where(f => String.Equals(f.Rel, "self", StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();

			if (result == null)
				throw new InvalidOperationException("FindSelfLink is not supported for this PWS Object");

			return result;
		}
		#endregion

		#region Object wrapper for fluent-type of interface.
		/// <summary>
		/// Return a wrapper object from the PWS Object. This can be used for simplifying operations such as following links between objects.
		/// The Token will be implicitly retrieved from the IPwsApplication GetDefaultToken method.
		/// </summary>
		/// <typeparam name="T">A PWS Object that implements IPwsObject_V1</typeparam>
		/// <param name="pwsObject">The PWS Object to be wrapped.</param>
		/// <param name="pwsApplication">A IPwsApplication instance.</param>
		/// <returns>Wrapper around PWS Object.</returns>
		public static IPwsObjectWrapper<T> AsWrapper<T>(this T pwsObject, IPwsApplication pwsApplication) where T : class, IPwsObject_V1
		{
			if (pwsObject == null)
				throw new ArgumentNullException("pwsObject", "AsWrapper requires a non-null PWS Object");

			if (pwsApplication == null)
				throw new ArgumentNullException("pwsApplication", "AsWrapper requires a non-null IPwsApplication instance");

			return pwsApplication.Wrap((T)pwsObject);
		}

		/// <summary>
		/// Return a wrapper object from the PWS Object. This can be used for simplifying operations such as following links between objects.
		/// The Token will be implicitly retrieved from the IPwsApplication GetDefaultToken method, if required.
		/// This overload returns an extended wrapper object which is serialisable.
		/// </summary>
		/// <typeparam name="T">A PWS Object that implements IPwsObject_V1</typeparam>
		/// <param name="pwsObject">The PWS Object to be wrapped.</param>
		/// <param name="pwsApplication">A IPwsApplication instance, to which the refreshed Token_V1 will be assigned as the default token </param>
		/// <returns>Wrapper around PWS Object.</returns>
/*		public static IPwsObjectWrapperSerialisable<T> AsWrapper<T>(this T pwsObject, IPwsApplication pwsApplication) where T : class, IPwsObjectSerialisable_V1<T>
		{
			if (pwsObject == null)
				throw new ArgumentNullException("pwsObject", "AsWrapper requires a non-null PWS Object");

			if (pwsApplication == null)
				throw new ArgumentNullException("pwsApplication", "AsWrapper requires a non-null IPwsApplication instance");

			return pwsApplication.Wrap((T)pwsObject);
		}*/
		#endregion

		#region Renewed Token request generation.
		/// <summary>
		/// Return a wrapper around a new Token_V1 object which will be used as a request for new credentials.
		/// The supplied IPwsApplication instance will have this token assigned to its default token, hence will overwrite any previous default token that may have assigned.
		/// </summary>
		/// <param name="token">Existing Token_V1 object</param>
		/// <param name="newPwsApplicationFunction">
		/// Function which returns an IPwsApplication instance, optionally based on the provided Token_V1 information (e.g. if the Client Application wishes to cache IPwsApplication instances per user login).
		/// (NB this will overwrite any previous default token that is set in the IPwsApplication instance)
		/// </param>
		/// <returns>Wrapper containing the token renewal request, which provides methods for amending the request e.g. account overrides.</returns>
		public static ITokenRequest_V1 AsRenewalRequest<T>(this T token, Func<Token_V1, IPwsApplication> newPwsApplicationFunction) where T : IPwsObjectWrapper<Token_V1>
		{
			return new TokenRequest_V1(token, newPwsApplicationFunction, token.PwsObject);
		}
		#endregion
	}
}
